<?php

namespace Yxy\YUtil;

use Exception;
use Godruoyi\Snowflake\RandomSequenceResolver;
use Godruoyi\Snowflake\Snowflake;
use Godruoyi\Snowflake\SnowflakeException;
use ReflectionFunction;

/**
 * 工具类
 */
class Utils
{
    /**
     * 雪花算法创建id
     * @param string $prefix 前缀
     * @param bool $replace 是否替换后六位为随机字符串
     * @return string
     * @throws SnowflakeException
     */
    public static function createUniqueCode(string $prefix = '', bool $replace = false): string
    {
        $snowflake = new Snowflake();
        if (PHP_INT_SIZE == 4) {
            $id = abs($snowflake->id());
        } else {
            $id = $snowflake->setStartTimeStamp(strtotime('2019-08-08') * 1000)->setSequenceResolver(new RandomSequenceResolver())->id();
        }

        if ($replace === true) {
            $replace = '';
            $chars = '0123456789';
            for ($i = 0; $i < 6; $i++) {
                $replace .= $chars[mt_rand(0, strlen($chars) - 1)];
            }
            $id = substr_replace($id, $replace, -6);
        }


        return $prefix . $id;
    }

    /**
     * 是否是url
     * @param string|null $url 要检查的字符串
     * @return false|string|null
     */
    public static function isUrl(string|null $url): false|string|null
    {
        if (preg_match("/^http(s)?:\\/\\/.+/", $url)) {
            return $url;
        }

        return false;
    }

    /**
     * 生成数字码
     * @param int $length
     * @return string
     */
    public static function makeCode(int $length = 6): string
    {
        return self::randString($length, 1);
    }

    /**
     *  随机字符串
     * @param string $length 长度
     * @param int $type 类型
     * @return string
     */
    public static function randString(string $length = '32', int $type = 4): string
    {
        $rand = '';
        $randStr = match ($type) {
            1 => '0123456789',
            2 => 'abcdefghijklmnopqrstuvwxyz',
            3 => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
            default => '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
        };
        $max = strlen($randStr) - 1;
        mt_srand((float)microtime() * 1000000);
        for ($i = 0; $i < $length; $i++) {
            $rand .= $randStr[mt_rand(0, $max)];
        }
        return $rand;
    }

    /**
     * SSL加密
     * @param string $string 需要加密的字符串
     * @param string $key 密钥
     * @return string
     */
    public static function aesEncrypt(string $string, string $key = "ONSPEED"): string
    {
        $data = openssl_encrypt($string, 'AES-128-ECB', $key, OPENSSL_RAW_DATA);
        return strtolower(bin2hex($data));
    }

    /**
     * SSL解密
     * @param string $string 需要解密的字符串
     * @param string $key 密钥
     * @return string
     */
    public static function aesDecrypt(string $string, string $key = "ONSPEED"): string
    {
        try {
            return openssl_decrypt(hex2bin($string), 'AES-128-ECB', $key, OPENSSL_RAW_DATA);
        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * 递归无限级分类权限
     * @param array $data 要转换的数组
     * @param int $pid 父级id
     * @param string $field1 父级字段
     * @param string $field2 子级关联的父级字段
     * @param string $field3 子级键值
     * @return array
     */
    public static function getTree(array $data, int $pid = 0, string $field1 = 'id', string $field2 = 'pid', string $field3 = 'children'): array
    {
        $arr = [];
        foreach ($data as $k => $v) {
            if ($v[$field2] == $pid) {
                $v[$field3] = self::getTree($data, $v[$field1]);
                $arr[] = $v;
            }
        }
        return $arr;
    }

    /**
     * 下划线转驼峰命名
     * @param string $str 转换的字符串
     * @return string
     */
    public static function toCamelCase(string $str): string
    {
        $array = explode('_', $str);
        $result = $array[0];
        $len = count($array);
        if ($len > 1) {
            for ($i = 1; $i < $len; $i++) {
                $result .= ucfirst($array[$i]);
            }
        }
        return $result;
    }

    /**
     * 驼峰命名转下划线
     * @param string $str 转换的字符串
     * @return string
     */
    public static function toUnderScore(string $str): string
    {
        $dStr = preg_replace_callback('/([A-Z]+)/', function ($match) {
            return '_' . strtolower($match[0]);
        }, $str);
        return trim(preg_replace('/_{2,}/', '_', $dStr), '_');
    }

    /**
     * 获取毫秒数
     * @return float
     */
    public static function millisecond(): float
    {
        list($mSec, $sec) = explode(' ', microtime());
        return (float)sprintf('%.0f', (floatval($mSec) + floatval($sec)) * 1000);
    }

    /**
     * 是否是时间戳
     * @param int|string $value 要检查的值
     * @return bool
     */
    public static function isTimestamp(int|string $value): bool
    {
        return is_numeric($value) && (int)$value >= 0 && (string)(int)$value === (string)$value;
    }

    /**
     * 处理时间戳转时间
     * @param int|string $time 要处理的时间
     * @param string $format 转换格式,同date函数
     * @return string
     */
    public static function handleDatetime(int|string $time, string $format = 'Y-m-d H:i'): string
    {
        if (self::isTimestamp($time)) {
            return date($format, (int)$time);
        }

        return date($format, strtotime($time));
    }

    /**
     * 处理时间转为时间戳
     * @param int|string $time 要处理的时间
     * @return int|bool
     */
    public static function handleStrToTime(int|string $time): int|bool
    {
        if (self::isTimestamp($time)) {
            return (int)$time;
        }

        return strtotime($time);
    }

    /**
     * 获取某天开始结束时间
     * @param string $time 时间
     * @return array
     */
    public static function getDayTime(string $time): array
    {
        if (!ctype_digit($time)) {
            $time = strtotime($time);
        }
        $start_time = date("Y-m-d 00:00:00", $time); //开始时间(00:00:00)
        $end_time = date("Y-m-d 23:59:59", $time); //结束时间(23:59:59)

        return [strtotime($start_time), strtotime($end_time)];
    }

    /**
     * 获取近七天的时间
     * @param string $time 初始时间
     * @param string $format 格式
     * @return array
     */
    public static function getWeeks(string $time = '', string $format = 'Y-m-d'): array
    {
        $time = $time != '' ? $time : time();
        //组合数据
        $date = [];
        for ($i = 7; $i < 14; $i++) {
            $date[] = date($format, strtotime('+' . $i - 7 . ' days', $time));
        }
        return $date;
    }

    /**
     * 获取给定时间月份信息
     * @param string $time 时间
     * @param bool $week 是否返回第一天周几
     * @return array
     */
    public static function getMonths(string $time = '', bool $week = false): array
    {
        if (empty($time)) {
            $time = time();
        } else {
            $time = self::handleStrToTime($time);
        }

        $year = date('Y', $time);
        $month = date('n', $time);
        $firstDay = strtotime("$year-$month-1");

        $months = [];

        $w = date('w', $firstDay);
        switch ($month) {
            case 2:
                $days = ($year % 400 == 0) || ($year % 4 && $year % 100 != 0) ? 29 : 28;
                break;
            case 4:
            case 6:
            case 9:
            case 11:
                $days = 30;
                break;
            default:
                $days = 31;
                break;
        }
        for ($i = 1; $i <= $days; $i++) {
            $date = $year . '-' . $month . '-' . $i;
            $months[] = [
                'date' => $date,
                'day' => $i < 0 ? '0' . $i : $i,
                'week' => date('w', self::handleStrToTime($date))
            ];
        }

        if ($week) {
            return [$months, $w];
        }

        return $months;
    }

    /**
     * 获取期间内所有日期
     * @param int|string $startTime 开始时间
     * @param int|string $endTime 结束时间
     * @return array
     */
    public static function periodDate(int|string $startTime, int|string $endTime): array
    {
        $startTime = self::handleStrToTime($startTime);
        $endTime = self::handleStrToTime($endTime);
        $arr = array();
        while ($startTime <= $endTime) {
            $arr[] = date('Y-m-d', $startTime);
            $startTime = strtotime('+1 day', $startTime);
        }
        return $arr;
    }

    /**
     * 今天周几
     * @param null $date 时间
     * @param bool $cn 是否要中文
     * @return int|string
     */
    function todayWeek($date = null, bool $cn = false): int|string
    {
        if (empty($date)) {
            $date = time();
        } else {
            $date = self::handleStrToTime($date);
        }

        $week = date("w", $date);

        $dayWeek = array(7, 1, 2, 3, 4, 5, 6)[$week];

        if ($cn === true) {
            return self::weekToCn($week, "星期");
        }

        return $dayWeek;
    }

    /**
     * 周几转汉字
     * @param int $week 数字
     * @param string $prefix 前缀文字
     * @return string
     */
    function weekToCn(int $week, string $prefix = '周'): string
    {
        $weeks_cn = [$prefix . '日', $prefix . '一', $prefix . '二', $prefix . '三', $prefix . '四', $prefix . '五', $prefix . '六'];

        return empty($weeks_cn[$week]) ? '无' : $weeks_cn[$week];
    }

    /**
     * 获取域名
     * @return string
     */
    public static function getDomain(): string
    {
        $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http';
        $host = $_SERVER['HTTP_HOST'];

        return $protocol . '://' . $host;
    }

    /**
     * 获取查看地址
     * @param string $value 路径
     * @return string
     */
    public static function getHttpPath(string $value): string
    {
        if (empty($value)) {
            return '';
        }
        $preg = "/^http(s)?:\\/\\/.+/";
        if (preg_match($preg, $value)) {
            return $value;
        }
        return self::getDomain() . $value;
    }

    /**
     * 删除字符串里所有指定字符
     * @param string $str 要操作的字符串
     * @param string $delete_str 要删除的字符串
     * @return mixed
     */
    public static function deleteStr(string $str, string $delete_str): mixed
    {
        $str_len = strlen($delete_str);
        $first = $str;
        $has_str = strpos($str, $delete_str);
        if ($has_str !== false) {
            $first = substr($str, 0, $has_str);
            $first .= substr($str, $has_str + $str_len);
            return self::deleteStr($first, $delete_str);
        }
        return $first;
    }


    /**
     * 获取根目录
     * @param string $path 路径
     * @return string
     */
    public static function getPublicPath(string $path = ''): string
    {
        if (function_exists("public_path")) {
            $public = public_path();
        } else {
            $public = $_SERVER['DOCUMENT_ROOT'];
        }


        $path = self::deleteStr($path, $public);

        return $public . $path;
    }

    /**
     * 数组按数量拆分
     * @param array $data 数组
     * @param int $num 每组长度
     * @return array
     */
    public static function arraySplit(array $data, int $num = 10): array
    {
        $arrRet = array();
        if (empty($data)) {
            return $arrRet;
        }

        $iCount = count($data) / $num;
        if (!is_int($iCount)) {
            $iCount = ceil($iCount);
        } else {
            $iCount += 1;
        }
        for ($i = 0; $i < $iCount; ++$i) {
            $arrInfos = array_slice($data, $i * $num, $num);
            if (empty($arrInfos)) {
                continue;
            }
            $arrRet[] = $arrInfos;
            unset($arrInfos);
        }

        return $arrRet;
    }

    /**
     * 是否是索引数组
     * @param array $arr 要检查的数组
     * @return bool
     */
    public static function isIndexArray(array $arr): bool
    {
        return count(array_filter(array_keys($arr), 'is_string')) <= 0;
    }

    /**
     * 根据key删除数组中指定元素
     * @param array $arr 数组
     * @param string $key /int  $key  键（key）
     * @return array
     */
    public static function arrayRemoveByKey(array $arr, string $key): array
    {
        if (!array_key_exists($key, $arr)) {
            return $arr;
        }
        $keys = array_keys($arr);
        $index = array_search($key, $keys);
        if ($index !== FALSE) {
            array_splice($arr, $index, 1);
        }

        return $arr;
    }


    /**
     * 获取创建图片文件的路径
     * @param string $path 要创建的路径
     * @param string $file_name 文件名称
     * @return string[]
     * @throws Exception
     */
    public static function makeImage(string $path, string $file_name = ''): array
    {
        return self::makeFilePath($path, $file_name);
    }

    /**
     * 获取生成文件的路径
     * @param string $path 路径
     * @param string $fileName 文件名
     * @param string $dir 最外层文件夹名称
     * @return string[]
     * @throws SnowflakeException
     */
    public static function makeFilePath(
        string $path = '',
        string $fileName = '',
        string $dir = 'y-images-y',
        string $suffix = 'png'
    ): array
    {
        if (!empty($fileName)) {
            $parts = explode('.', $fileName);
            if (count($parts) > 1) {
                $suffix = end($parts);
                $fileName = $parts[0];
            }
        }
        $name = empty($fileName) ? self::createUniqueCode(16) : $fileName;

        $public = self::getPublicPath();

        $resDir = '/' . $dir . '/' . $path;

        $filename = '/' . $name . '.' . $suffix;

        $mkdir = $public . $resDir;

        if (!is_dir($mkdir)) {
            mkdir($mkdir, 0755, true);
        }

        return [$public . $dir . $filename, $dir . $filename];
    }

    /**
     * 获取字符首字母
     * @param mixed $str 要获取的字符串
     * @return string|null
     */
    public static function getFirstCharters(mixed $str): ?string
    {
        if (empty($str)) {
            return '';
        }
        if ($str == "衢州市") {
            return 'Q';
        } elseif ($str == "亳州市") {
            return 'B';
        } elseif ($str == "濮阳市") {
            return 'P';
        } elseif ($str == "漯河市") {
            return 'L';
        } elseif ($str == "儋州市") {
            return 'D';
        } elseif ($str == "泸州市") {
            return 'L';
        } elseif ($str == "重庆城区") {
            return 'C';
        } elseif ($str == "重庆郊县") {
            return 'C';
        } elseif ($str == "氹仔岛") {
            return 'D';
        }

        $fchar = ord($str[0]);

        if ($fchar >= ord('A') && $fchar <= ord('z')) {
            return strtoupper($str[0]);
        }

        $s1 = iconv('UTF-8', 'gbk//TRANSLIT//IGNORE', $str);
        $s2 = iconv('gbk//TRANSLIT//IGNORE', 'UTF-8', $s1);
        $s = $s2 == $str ? $s1 : $str;
        $asc = ord($s[0]) * 256 + ord($s[1]) - 65536;

        if ($asc >= -20319 && $asc <= -20284) {
            return 'A';
        }

        if ($asc >= -20283 && $asc <= -19776) {
            return 'B';
        }

        if ($asc >= -19775 && $asc <= -19219) {
            return 'C';
        }

        if ($asc >= -19218 && $asc <= -18711) {
            return 'D';
        }

        if ($asc >= -18710 && $asc <= -18527) {
            return 'E';
        }

        if ($asc >= -18526 && $asc <= -18240) {
            return 'F';
        }

        if ($asc >= -18239 && $asc <= -17923) {
            return 'G';
        }
        if ($asc >= -17922 && $asc <= -17418) {
            return 'H';
        }

        if ($asc >= -17417 && $asc <= -16475) {
            return 'J';
        }

        if ($asc >= -16474 && $asc <= -16213) {
            return 'K';
        }

        if ($asc >= -16212 && $asc <= -15641) {
            return 'L';
        }

        if ($asc >= -15640 && $asc <= -15166) {
            return 'M';
        }

        if ($asc >= -15165 && $asc <= -14923) {
            return 'N';
        }

        if ($asc >= -14922 && $asc <= -14915) {
            return 'O';
        }
        if ($asc >= -14914 && $asc <= -14631) {
            return 'P';
        }

        if ($asc >= -14630 && $asc <= -14150) {
            return 'Q';
        }

        if ($asc >= -14149 && $asc <= -14091) {
            return 'R';
        }

        if ($asc >= -14090 && $asc <= -13319) {
            return 'S';
        }

        if ($asc >= -13318 && $asc <= -12839) {
            return 'T';
        }

        if ($asc >= -12838 && $asc <= -12557) {
            return 'W';
        }

        if ($asc >= -12556 && $asc <= -11848) {
            return 'X';
        }

        if ($asc >= -11847 && $asc <= -11056) {
            return 'Y';
        }

        if ($asc >= -11055 && $asc <= -10247) {
            return 'Z';
        }
        if ($asc > -11055) {
            return '#';
        }

        return null;
    }

    /**
     * 执行回调函数
     * @param callable $callback 回调函数
     * @param array $resultArray 参数名和参数值键值对
     * @return void
     * @throws \ReflectionException
     */
    public static function handleCallable(callable $callback, array $resultArray = []): void
    {
        if (is_callable($callback)) {
            $reflection = new ReflectionFunction($callback);
            $params = $reflection->getParameters();
            $args = [];
            foreach ($params as $param) {
                $name = $param->getName();
                $keys = array_keys($resultArray);
                if (in_array($name, $keys)) {
                    $args[] = $resultArray[$name];
                } else {
                    $args[] = "";
                }
            }
            $callback(...$args);
        }
    }
}